package io.yunho.alg.snowflake.util;

import cn.hutool.json.JSONObject;

import java.util.Calendar;
import java.util.Date;

/**
 * @author: Jason
 * @date: 2020/12/28 11:18
 * @description:
 */
public class SnowFlakeIdWorker {
    /**
     * 工作机器ID（0~31）
     */
    private long workerId;
    /**
     * 数据中心ID（0~31）
     */
    private long datacenterId;
    /**
     * 毫秒内序列（0~4095）
     */
    private long sequence=0L;
    /**
     * 上次生成ID的时间戳
     */
    private long lastTimestamp=-1L;
    /**
     * 开始时间戳（2019-01-01）1420041600000L
     * 2018-01-01 1514736000000L
     */
    private final long twepoch=1514736000000L;
    /**
     * 机器ID所占的位数
     */
    private final long workerIdBits = 5L;
    /**
     * 数据标识ID所占的位数
     */
    private final long datacenterIdBits = 5L;
    /**
     * 支持的最大机器id，结果是31（这个移位算法可以很快的计算出几位二进制数所能标识的最大十进制数）
     */
    private final long maxWorkerId = -1L^(-1L<<workerIdBits);
    /**
     * 支持的最大数据标识id，结果是31
     */
    private final long maxDatacenterId = -1L^(-1L<<datacenterIdBits);
    /**
     * 序列在ID中所占的位数
     */
    private final long sequenceBits = 12L;
    /**
     * 机器ID向左移位12位
     */
    private final long workerIdShift = sequenceBits;
    /**
     * 数据标识ID向左移位17位（12+5）
     */
    private final long datacenterIdShift = sequenceBits+workerIdBits;
    /**
     * 时间戳向左移位22位（12+5+5）
     */
    private final long timestampLeftShift = sequenceBits+workerIdBits+datacenterIdBits;
    /**
     * 生成序列的掩码，这里为4095 (0b111111111111=0xfff=4095)
     */
    private final long sequenceMask = -1L ^ (-1L << sequenceBits);

    public SnowFlakeIdWorker(long workerId,long datacenterId){
        if(workerId>maxWorkerId||workerId<0){
            throw new IllegalArgumentException(String.format("机器标识不能比%d大，不能比0小",maxWorkerId));
        }
        if(datacenterId>maxDatacenterId||datacenterId<0){
            throw new IllegalArgumentException(String.format("数据标识不能比%d大，不能比0小",maxDatacenterId));
        }
        this.workerId = workerId;
        this.datacenterId = datacenterId;
    }

    /**
     * 获取下一个ID（线程安全）
     * @return
     */
    public synchronized long nextValue() {
        long timestamp = timeGen();

        // 如果当前时间小于上一次ID生成的时间戳，说明系统时钟回退过这个时候应当抛出异常
        if (timestamp < lastTimestamp) {
            throw new RuntimeException("[SnowflakeSequence-nextValue] 当前时间小于上次生成序列号的时间，时间被回退了，请确认服务器时间的设置.");
        }

        // 如果是同一时间生成的，则进行毫秒内序列
        if (lastTimestamp == timestamp) {
            sequence = (sequence + 1) & sequenceMask;
            // 毫秒内序列溢出
            if (sequence == 0) {
                // 阻塞到下一个毫秒,获得新的时间戳
                timestamp = tilNextMillis(lastTimestamp);
            }
        }
        else {
            // 时间戳改变，毫秒内序列重置
            sequence = 0L;
        }

        // 上次生成ID的时间截
        lastTimestamp = timestamp;
        System.out.println("timestamp:"+timestamp);
        // 移位并通过或运算拼到一起组成64位的ID
        return ((timestamp - twepoch) << timestampLeftShift)
                | (datacenterId << datacenterIdShift)
                | (workerId << workerIdShift)
                | sequence;
    }

    /**
     * 阻塞到下一个毫秒，直到获得新的时间戳
     * @param lastTimestamp 上次生成ID的时间截
     * @return 当前时间戳
     */
    private long tilNextMillis(long lastTimestamp) {
        long timestamp = timeGen();
        while (timestamp <= lastTimestamp) {
            timestamp = timeGen();
        }
        return timestamp;
    }

    /**
     * 返回以毫秒为单位的当前时间
     * @return 当前时间(毫秒)
     */
    private long timeGen() {
        return System.currentTimeMillis();
    }

    public void setWorkerId(long workerId) {
        if (workerId > maxWorkerId) {
            throw new IllegalArgumentException("[SnowflakeSequence-setWorkerId] workerId 不能大于31.");
        }

        this.workerId = workerId;
    }

    public void setDatacenterId(long datacenterId) {
        if (datacenterId > maxDatacenterId) {
            throw new IllegalArgumentException("[SnowflakeSequence-setDatacenterId] datacenterId 不能大于31.");
        }

        this.datacenterId = datacenterId;
    }

    /**
     * 下一个生成序号（带格式）
     * @return
     */
    public String nextNo() {
        return String.valueOf(nextValue());
    }

    /**
     * 反解析
     * @param id
     * @return
     */
    public JSONObject parseInfo(long id) {
        String sonwFlakeId = Long.toBinaryString(id);
        int len = sonwFlakeId.length();
        JSONObject jsonObject = new JSONObject();
        int sequenceStart = (int) (len < workerIdShift ? 0 : len - workerIdShift);
        int workerStart = (int) (len < datacenterIdShift ? 0 : len - datacenterIdShift);
        int timeStart = (int) (len < timestampLeftShift ? 0 : len - timestampLeftShift);
        String sequence = sonwFlakeId.substring(sequenceStart, len);
        String workerId = sequenceStart == 0 ? "0" : sonwFlakeId.substring(workerStart, sequenceStart);
        String dataCenterId = workerStart == 0 ? "0" : sonwFlakeId.substring(timeStart, workerStart);
        String time = timeStart == 0 ? "0" : sonwFlakeId.substring(0, timeStart);
        int sequenceInt = Integer.valueOf(sequence, 2);
        jsonObject.put("sequence", sequenceInt);
        int workerIdInt = Integer.valueOf(workerId, 2);
        jsonObject.put("workerId", workerIdInt);
        int dataCenterIdInt = Integer.valueOf(dataCenterId, 2);
        jsonObject.put("dataCenter", dataCenterIdInt);
        long diffTime = Long.parseLong(time, 2);
        long timeLong = diffTime + twepoch;
        Date date = fromatTime(timeLong);
        jsonObject.put("date", date);
        return jsonObject;
    }
    /**
     * 解析时间戳
     * @param id
     * @return
     */
    public Date getSonwFlakeDate(long id) {
        JSONObject jsonObject = parseInfo(id);
        Object dateObj = jsonObject.get("date");
        return (Date) dateObj;
    }
    private Date fromatTime(long date) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTimeInMillis(date);
        return calendar.getTime();
    }
    public static void main(String[] args) {
        SnowFlakeIdWorker snowFlakeIdWorker = new SnowFlakeIdWorker(29,28);
//        for (int i=0;i<10;i++){
//            String id = snowFlakeIdWorker.nextNo();
//            System.out.println(id+"\t"+id.length());
//            System.out.println("-------------------");
//        }
//        long sequenceBits = 12L;
//        long sequenceBits_4095 = 4095L;
//        long s_4096 = 4096L;
//        //这段代码通过位与运算保证计算的结果范围始终是 0-4095 ！
////        System.out.println(4097L & sequenceBits_4095);
//        long t = -1L;
//        System.out.println((-1L << sequenceBits));
//        System.out.println(-1L ^ -4096);//4095
//        System.out.println(-1L << 12L);

//        long timestamp = 1505914988849L;
//        long twepoch = 1288834974657L;
//        long datacenterId = 17L;
//        long workerId = 25L;
//        long sequence = 0L;
//
//        System.out.printf("\ntimestamp: %d \n",timestamp);
//        System.out.printf("twepoch: %d \n",twepoch);
//        System.out.printf("datacenterId: %d \n",datacenterId);
//        System.out.printf("workerId: %d \n",workerId);
//        System.out.printf("sequence: %d \n",sequence);
//        System.out.println();
//        System.out.printf("(timestamp - twepoch): %d \n",(timestamp - twepoch));
//        System.out.printf("((timestamp - twepoch) << 22L): %d \n",((timestamp - twepoch) << 22L));
//        System.out.printf("(datacenterId << 17L): %d \n" ,(datacenterId << 17L));
//        System.out.printf("(workerId << 12L): %d \n",(workerId << 12L));
//        System.out.printf("sequence: %d \n",sequence);
//
//        long result = ((timestamp - twepoch) << 22L) |
//                (datacenterId << 17L) |
//                (workerId << 12L) |
//                sequence;
//        System.out.println(result);
        //910499571847892992
        long id = snowFlakeIdWorker.nextValue();
        JSONObject jsonObject = snowFlakeIdWorker.parseInfo(id);
        System.out.println("------------------------------------------");
        System.out.println(jsonObject);
        Object dateObj = jsonObject.get("date");
        System.out.println("date:" + dateObj);
        System.out.println("------------------------------------------");
    }

}
